home *** CD-ROM | disk | FTP | other *** search
/ Tricks of the Mac Game Programming Gurus / TricksOfTheMacGameProgrammingGurus.iso / Book Chapters / 07 - Mechanics - Environment / Dungeon E / DungeonE.c < prev    next >
Text File  |  1995-04-01  |  28KB  |  1,010 lines

  1.  
  2. /*DungeonE – prototype game example for the Mac Game book*/
  3. /*By Ingemar Ragnemalm 1995*/
  4. /**/
  5. /*This game is somewhat similar to MemoryGame, in that it uses a grid, represented*/
  6. /*by an array. The game is a typical (though extremely simplified) dungeon-digging game,*/
  7. /*where the objective is to collect treasures and fight monsters.*/
  8.  
  9. /*Representation:*/
  10. /*The array tileArr holds nearly all information we need. telling what is in each space in the grid.*/
  11. /*Monsters and treasures are only represented this way. In a real game, you may wish to*/
  12. /*keep a list of all monster and treasure positions, to avoid scanning for them and to keep more*/
  13. /*information about each.*/
  14. /*The game also keeps an array that tells what spaces are known to the player (tilesKnown), and*/
  15. /*the player position (playerPosition), so we don't have to scan for it all the time.*/
  16. /*The combat system is extremely simple. If you try moving to an enemy, you have 60% chance to*/
  17. /*hit it and kill it. If an enemy tries to move to you, it has 50% chance to hit, reducing your hit points*/
  18. /*by one.*/
  19. /**/
  20. /*The program ends when we select "Quit".*/
  21.  
  22. /* This version is based on Dungeon 2, and adds the following features:*/
  23. /* - Game entity list, describing all game entities in a list rather than*/
  24. /* as parts of an array. */
  25.  
  26.  
  27. #include <Sound.h>
  28.  
  29. /*Size of the array*/
  30. #define    kArraySizeH 15
  31. #define    kArraySizeV 12
  32.  
  33. /*Size of the tiles*/
  34. #define    kTileSizeH 32
  35. #define    kTileSizeV 32
  36.  
  37. /*Menu ids*/
  38. #define    appleID 127
  39. #define    fileID 128
  40.  
  41. /* A macro for taking the abs of a value */
  42. #define abs(x) (x>0?x:-x)
  43.  
  44. /* All the possible states of a tile (space in the dungeon) */
  45. //typedef enum {empty, wall, player, enemy, tempEnemy, gold, exitPos} TileState;
  46. typedef enum {empty, wall, exitPos} TileState;
  47.  
  48. /* The window pointer */
  49. WindowPtr myWindow;
  50.  
  51. /* Arrays describing the dungeon */
  52.  
  53. /* What tiles have we seen? */
  54. Boolean tileKnown[kArraySizeH][kArraySizeV];
  55. /* What does each tile contain? */
  56. /* tileArray now only holds static objects */
  57. TileState tileArray[kArraySizeH][kArraySizeV];
  58.  
  59. /*Variables describing the player:*/
  60. //Point playerPosition; -> player->position
  61. short playerHitPoints; // NOT -> player->value
  62.  
  63. /* A boolean telling if we should quit yet or not */
  64. Boolean gDone = false;
  65.  
  66. /*Pictures*/
  67. PicHandle floorTile;
  68. PicHandle wallTile;
  69. PicHandle exitTile;
  70. PicHandle playerTile;
  71. PicHandle enemyTile;
  72. PicHandle goldTile;
  73.  
  74. /*All 8 directions as vectors*/
  75. Point directionTable[8] = { { 0, 1 },
  76.                           {-1, 1 },
  77.                           {-1, 0 },
  78.                           {-1,-1 },
  79.                           { 0,-1 },
  80.                           { 1,-1 },
  81.                           { 1, 0 },
  82.                           { 1, 1 }};
  83.  
  84.  
  85.  
  86. /************************************************/
  87. /* Types and variables for game entity handling */
  88. /************************************************/
  89.  
  90. /* All the possible kinds of game entities */
  91. typedef enum {playerEntity, enemyEntity, goldEntity} EntityType;
  92.  
  93. /*Variables describing a game entity*/
  94. typedef struct GameEntityRecord {
  95.     EntityType        kind;                    /* What kind of entity? */
  96.     Point            position;                /* Where in the grid? */
  97.     short            value;                    /* Hit points or point value */
  98.     struct GameEntityRecord    *previous, *next;    /* Next and previous entity in the list */
  99. } GameEntityRecord;
  100.  
  101. typedef GameEntityRecord *GameEntityPtr;        /* Define a handle type to it */
  102.  
  103.  
  104. /* A global pointer is the root of the entity list */
  105. GameEntityPtr    gEntityList = nil;
  106.  
  107. /* The player entity must be easily accessible */
  108. GameEntityPtr    player;
  109.  
  110. /* tileArray now only holds static objects */
  111. /* What entity is in each tile? */
  112. GameEntityPtr entityArray[kArraySizeH][kArraySizeV];
  113.  
  114. /*** End of game entity handling types and variables ***/
  115.  
  116.  
  117.  
  118.  
  119. /* A function that generates a value in the interval 0..range-1 */
  120.  
  121. static short Rand(short range)
  122. {
  123.     return (Random () & 0x7fff) % range;
  124. }; /*Rand*/
  125.  
  126.  
  127. /* Draw a tile */
  128.  
  129. static void DrawTile(short h, short v)
  130. {
  131.     Rect tileRectangle;
  132.  
  133.     SetRect(&tileRectangle, h * kTileSizeH, v * kTileSizeV, (h + 1) * kTileSizeH, (v + 1) * kTileSizeV);
  134.     if ( tileKnown[h][v] )
  135.     {
  136.         switch ( tileArray[h][v] )
  137.         {
  138.             case empty: 
  139.                 DrawPicture(floorTile, &tileRectangle); break;
  140.             case wall: 
  141.                 DrawPicture(wallTile, &tileRectangle); break;
  142.             case exitPos: 
  143.                 DrawPicture(exitTile, &tileRectangle); break;
  144.             default:
  145.                 PaintRect(&tileRectangle);
  146.         }
  147.         if (entityArray[h][v] != nil)
  148.             switch (entityArray[h][v]->kind)
  149.             {
  150.                 case playerEntity:
  151.                     DrawPicture(playerTile, &tileRectangle); break;
  152.                 case enemyEntity:
  153.                     DrawPicture(enemyTile, &tileRectangle); break;
  154.                 case goldEntity:
  155.                     DrawPicture(goldTile, &tileRectangle); break;
  156.                 default:
  157.                     ;
  158.             }
  159.     }
  160.     else
  161.         PaintRect(&tileRectangle);
  162. } /*DrawTile*/
  163.  
  164.  
  165.  
  166. /*************************************/
  167. /* Routines for game entity handling */
  168. /*************************************/
  169.  
  170. /* NewEntity allocates space for a new entity and puts it in the entity list */
  171. static GameEntityPtr NewEntity()
  172. {
  173.     GameEntityPtr    newEntity;
  174.  
  175.     newEntity = (GameEntityPtr) NewPtr(sizeof(GameEntityRecord));
  176.     if (newEntity == nil) return nil;
  177.     if (gEntityList != nil)
  178.     {
  179.         gEntityList->previous = newEntity;
  180.     }
  181.     newEntity->next = gEntityList;
  182.     newEntity->previous = nil;
  183.     gEntityList = newEntity;
  184.     return newEntity;
  185. } /*NewEntity*/
  186.  
  187. /* DisposeEntity removes an entity from the list and disposes it. */
  188. static void DisposeEntity(GameEntityPtr doomedEntity)
  189. {
  190.     if (doomedEntity == nil) return;
  191.     if (doomedEntity->next != nil)
  192.         doomedEntity->next->previous = doomedEntity->previous;
  193.     if (doomedEntity->previous != nil)
  194.         doomedEntity->previous->next = doomedEntity->next;
  195.     if (doomedEntity == gEntityList)
  196.         gEntityList = doomedEntity->next;
  197.     DisposePtr((Ptr)doomedEntity);
  198. } /*DisposeEntity*/
  199.  
  200.  
  201. /* MakeEntity builds an entity with the given values and puts it into the entityArray */
  202. static GameEntityPtr MakeEntity(EntityType eType, short hPos, short vPos, short value)
  203. {
  204.     GameEntityPtr newEntity;
  205.  
  206.     newEntity = NewEntity();
  207.     if (newEntity == nil) return nil;
  208.     newEntity->position.h = hPos;
  209.     newEntity->position.v = vPos;
  210.     newEntity->kind = eType;
  211.     newEntity->value = value;
  212. /* Border checking here would be good for safety */
  213.     entityArray[newEntity->position.h][newEntity->position.v] = newEntity;
  214.     DrawTile(newEntity->position.h, newEntity->position.v);
  215.     return newEntity;
  216. } /*MakeEntity*/
  217.  
  218. /* KillEntity disposes an entity, removes it from EntityArray and erases it */
  219. static void KillEntity(GameEntityPtr doomedEntity)
  220. {
  221.     if (doomedEntity == nil) return;
  222.     if (entityArray[doomedEntity->position.h][doomedEntity->position.v] == doomedEntity)
  223.         entityArray[doomedEntity->position.h][doomedEntity->position.v] = nil;
  224.     DrawTile(doomedEntity->position.h, doomedEntity->position.v);
  225.     DisposeEntity(doomedEntity);
  226. } /*KillEntity*/
  227.  
  228. /*** End of game entity handling routines ***/
  229.  
  230.  
  231.  
  232. /*** Score handling and display ***/
  233.  
  234. /*Number of extra pixels below game graphics*/
  235. #define    kScoreFieldHeight    15
  236. /*Base line for text, how far from bottom of window*/
  237. #define    kScoreBaseLine    3
  238.  
  239. /*The score is a global*/
  240. long gGold, gExperience;
  241.  
  242.  
  243. static void DrawScore()
  244. {
  245.     Str255    tempString;
  246.     Rect    blankRect;
  247.     
  248.     SetPort(myWindow);
  249.  
  250.     blankRect = myWindow->portRect;
  251.     blankRect.top = myWindow->portRect.bottom - kScoreFieldHeight;
  252.     EraseRect(&blankRect);
  253.  
  254.     MoveTo(myWindow->portRect.right * 1 / 5, myWindow->portRect.bottom - kScoreBaseLine);
  255.     DrawString("\pGold: ");
  256.     NumToString(gGold, tempString);
  257.     DrawString(tempString);
  258.  
  259.     MoveTo(myWindow->portRect.right * 2 / 5, myWindow->portRect.bottom - kScoreBaseLine);
  260.     DrawString("\pExperience: ");
  261.     NumToString(gExperience, tempString);
  262.     DrawString(tempString);
  263.  
  264.     MoveTo(myWindow->portRect.right * 3 / 5, myWindow->portRect.bottom - kScoreBaseLine);
  265.     DrawString("\pHit points: ");
  266.     NumToString(playerHitPoints, tempString);
  267.     DrawString(tempString);
  268.  
  269. } /*DrawScore*/
  270.  
  271.  
  272. static void AddScore(long addToGold, long addToExperience)
  273. {
  274.     gGold += addToGold;
  275.     gExperience += addToExperience;
  276.     DrawScore();
  277. } /*AddScore*/
  278.  
  279.  
  280. /* Set all tiles around the player to be known, and draw them if they were not known before. */
  281.  
  282. static void ShowAroundPlayer()
  283. {
  284.     short h, v;
  285.  
  286.     for ( h = player->position.h - 1 ; h <= player->position.h + 1 ; h++)
  287.         for ( v = player->position.v - 1 ; v <= player->position.v + 1 ; v++)
  288.             if ( ! tileKnown[h][v] )
  289.             {
  290.                 tileKnown[h][v] = true;
  291.                 DrawTile(h, v);
  292.             }
  293. } /*ShowAroundPlayer*/
  294.  
  295.  
  296. /* The level generation routine */
  297.  
  298. static void CreateLevel()
  299. {
  300.     Rect roomRect[10];
  301.     short room;
  302.     short height, width;
  303.     short h, v, h1, v1, h2, v2;
  304.     short numRooms;
  305.     short numTreasures, numMonsters;
  306.     short i;
  307.  
  308. /*For each tile position, we set the initial state.*/
  309. /**/
  310. /*Here we generate the dungeon randomly, with a rather simple algorithm.*/
  311. /*Most real games use pre-designed levels, preferrably stored in resources.*/
  312.  
  313.  
  314. /*Dungeon generation algorithm:*/
  315. /*We select a number of rooms to be placed, 3-10*/
  316. /*Each room is randomly assigned a size, and then a position.*/
  317. /*In each room we may put monsters and/or treasures.*/
  318. /*From each room except the last, we make a path from the room to the next.*/
  319. /*This guarantees that all rooms are connected.*/
  320.  
  321.  
  322. /*First fill the entire dungeon with walls!*/
  323.     for ( h = 0 ; h < kArraySizeH ; h++)
  324.         for ( v = 0 ; v < kArraySizeV ; v++)
  325.             tileArray[h][v] = wall;
  326.  
  327. /*All tiles are unknown when we start*/
  328. /*This must be in the beginning now, since MakeEntity draws*/
  329. /*the new entity*/
  330.     for ( h = 0 ; h < kArraySizeH ; h++)
  331.         for ( v = 0 ; v < kArraySizeV ; v++)
  332.             tileKnown[h][v] = false;
  333.  
  334. /*Dispose all old entities*/
  335.     while (gEntityList != nil) DisposeEntity(gEntityList);
  336.  
  337. /*Fill the entityArray with nil - there's nobody there!*/
  338.     for ( h = 0 ; h < kArraySizeH ; h++)
  339.         for ( v = 0 ; v < kArraySizeV ; v++)
  340.             entityArray[h][v] = nil;
  341.  
  342.     numRooms = 3 + Rand(4); /*3 to 6 rooms*/
  343.  
  344. /*Create each room*/
  345.     for ( room = 0 ; room <= numRooms - 1 ; room++)
  346.     {
  347.         height = 1 + Rand(5 - numRooms / 2);
  348.         width = 1 + Rand(5 - numRooms / 2);
  349.         roomRect[room].top = 1 + Rand(kArraySizeV - height - 2);
  350.         roomRect[room].bottom = roomRect[room].top + height;
  351.         roomRect[room].left = 1 + Rand(kArraySizeH - width - 2);
  352.         roomRect[room].right = roomRect[room].left + width;
  353.  
  354.         for ( h = roomRect[room].left ; h <= roomRect[room].right ; h++)
  355.             for ( v = roomRect[room].top ; v <= roomRect[room].bottom ; v++)
  356.                     tileArray[h][v] = empty;
  357.     };
  358.  
  359. /*Make paths between all rooms*/
  360.     for ( room = 0 ; room <= numRooms - 2 ; room++)
  361.     {
  362. /*We make a path from h1, h2, to h2, v2*/
  363.         h1 = roomRect[room].left + Rand(roomRect[room].right - roomRect[room].left + 1);
  364.         v1 = roomRect[room].top + Rand(roomRect[room].bottom - roomRect[room].top + 1);
  365.         h2 = roomRect[room + 1].left + Rand(roomRect[room + 1].right - roomRect[room + 1].left + 1);
  366.         v2 = roomRect[room + 1].top + Rand(roomRect[room + 1].bottom - roomRect[room + 1].top + 1);
  367.  
  368. /*First move along the h axis*/
  369.         if ( h1 < h2 )
  370.             for ( h = h1 ; h <= h2 ; h++)
  371.                 tileArray[h][v1] = empty;
  372.         else
  373.             for ( h = h1 ; h >= h2 ; h--)
  374.                 tileArray[h][v1] = empty;
  375. /*And then along the v axis*/
  376.         if ( v1 < v2 )
  377.             for ( v = v1 ; v <= v2 ; v++)
  378.                 tileArray[h2][v] = empty;
  379.         else
  380.             for ( v = v1 ; v >= v2 ; v--)
  381.                 tileArray[h2][v] = empty;
  382.     };
  383.  
  384. /*Now populate the rooms!*/
  385.     for ( room = 1 ; room <= numRooms - 1 ; room++)
  386.     {
  387.         numTreasures = Rand(3);        /*0 to 2 treasures*/
  388.         for ( i = 1 ; i <= numTreasures ; i++)
  389.         {
  390.             h = roomRect[room].left + Rand(roomRect[room].right - roomRect[room].left + 1);
  391.             v = roomRect[room].top + Rand(roomRect[room].bottom - roomRect[room].top + 1);
  392.             if (entityArray[h][v] == nil)
  393.                 MakeEntity(goldEntity, h, v, Rand(100) + 1);
  394. //            tileArray[h][v] = gold;
  395.         };
  396.         numMonsters = Rand(2);        /*0 to 1 monsters*/
  397.         for ( i = 1 ; i <= numMonsters; i++)
  398.         {
  399.             h = roomRect[room].left + Rand(roomRect[room].right - roomRect[room].left + 1);
  400.             v = roomRect[room].top + Rand(roomRect[room].bottom - roomRect[room].top + 1);
  401.             if (entityArray[h][v] == nil)
  402.                 MakeEntity(enemyEntity, h, v, 2);
  403. //            tileArray[h][v] = enemy;
  404.         };
  405.     };
  406.  
  407. /*Finally, place the player in the first room and the exit in the last. Also check that the exit is*/
  408. /*not the same position as the player! (It can happen, since we don't mak sure that rooms don't*/
  409. /*overlap!)*/
  410.  
  411. /*Player position:*/
  412.     h = roomRect[0].left + Rand(roomRect[0].right - roomRect[0].left + 1);
  413.     v = roomRect[0].top + Rand(roomRect[0].bottom - roomRect[0].top + 1);
  414.     if (entityArray[h][v] != nil)
  415.         KillEntity(entityArray[h][v]);
  416.     player = MakeEntity(playerEntity, h, v, 5);
  417. //    tileArray[h][v] = player;
  418. /*Exit position:*/
  419.     h = roomRect[numRooms - 1].left + Rand(roomRect[numRooms - 1].right - roomRect[numRooms - 1].left + 1);
  420.     v = roomRect[numRooms - 1].top + Rand(roomRect[numRooms - 1].bottom - roomRect[numRooms - 1].top + 1);
  421. /*But please don't overwrite the player with the exit!*/
  422.     if ( h == player->position.h )
  423.         if ( v == player->position.v )
  424.         {
  425. /*Try another room:*/
  426.             h = roomRect[numRooms - 2].left + Rand(roomRect[numRooms - 2].right - roomRect[numRooms - 2].left + 1);
  427.             v = roomRect[numRooms - 2].top + Rand(roomRect[numRooms - 2].bottom - roomRect[numRooms - 2].top + 1);
  428.  
  429. /*Still failure? Darn. Just take a space next to the player.*/
  430.             if ( h == player->position.h )
  431.                 if ( v == player->position.v )
  432.                     if ( h < kArraySizeH )
  433.                         h = h + 1;
  434.                     else
  435.                         h = h - 1;
  436.         };
  437. /*OK, that's enough. Set the exit!*/
  438.     tileArray[h][v] = exitPos;
  439.  
  440. /*All tiles are unknown when we start*/
  441. /*…except the ones around the player*/
  442.  
  443. /*Make the spaces around the player known*/
  444.     ShowAroundPlayer();
  445.  
  446. /*Draw all tiles!*/
  447.     for ( h = 0 ; h < kArraySizeH ; h++)
  448.         for ( v = 0 ; v < kArraySizeV ; v++)
  449.             DrawTile(h, v);
  450. } /*CreateLevel*/
  451.  
  452.  
  453. /* Move an enemy */
  454.  
  455. static void MoveEnemy(GameEntityPtr theEnemy /*short h, short v*/)
  456. {
  457.         short dist;
  458.         short newh, newv;
  459.         short dir;
  460.  
  461. /*1: decide if we are close to enough to the player to "hear" the player*/
  462.  
  463.     dist = abs(theEnemy->position.h - player->position.h) + abs(theEnemy->position.v - player->position.v); /*City Block distance*/
  464.  
  465. /*2: Make a suggested destination, newh, newv*/
  466.  
  467.     if ( dist < Rand(15) )                    /*Move towards the player*/
  468.     {
  469.         if ( theEnemy->position.h < player->position.h )
  470.             newh = theEnemy->position.h + 1;
  471.         else if ( theEnemy->position.h > player->position.h )
  472.             newh = theEnemy->position.h - 1;
  473.         else
  474.             newh = theEnemy->position.h;
  475.         if ( theEnemy->position.v < player->position.v )
  476.             newv = theEnemy->position.v + 1;
  477.         else if ( theEnemy->position.v > player->position.v )
  478.             newv = theEnemy->position.v - 1;
  479.         else
  480.             newv = theEnemy->position.v;
  481.     }
  482.     else                                    /*Move randomly*/
  483.     { 
  484.         dir = Rand(8);
  485.         newh = theEnemy->position.h + directionTable[dir].h;
  486.         newv = theEnemy->position.v + directionTable[dir].v;
  487.     }
  488.  
  489. /*3: Check what is in the destination and take appropriate action (move, fight)*/
  490.  
  491.     switch ( tileArray[newh][newv] )
  492.     {
  493.     case empty:
  494.     case exitPos:
  495. /*                tileArray[newh][newv] = tempEnemy;    /*We can't use "enemy", since then we might process it again in the same move!*/
  496. /*                tileArray[h][v] = empty;*/
  497.         if (entityArray[newh][newv] == player)
  498.         {
  499.             if ( Rand(10) > 5 )                            /*Does it hit?*/
  500.             {                                        /*Enemy hits player!*/
  501.                 playerHitPoints--;                    /*Reduce player hit points*/
  502.                 DrawScore();                        /*Draw score to display the lower hit points!*/
  503.                 if ( playerHitPoints > 0 )
  504.                 {                                        /*Player still lives!*/
  505.                     if ( noErr != SndPlay(nil, (SndListHandle)GetNamedResource('snd ', "\pPlayer hit"), false) )
  506.                         ;
  507.                 }
  508.                 else
  509.                 {                                /*Player died!*/
  510.                     if ( noErr != SndPlay(nil, (SndListHandle)GetNamedResource('snd ', "\pPlayer died"), false) )
  511.                         ;
  512.                 };
  513.             }
  514.             else
  515.             {                                    /*Miss!*/
  516.                 if ( noErr != SndPlay(nil, (SndListHandle)GetNamedResource('snd ', "\pPlayer miss"), false) )
  517.                     ;
  518.             }
  519.         }
  520.         else
  521.         if (entityArray[newh][newv] == nil)
  522.         {
  523.             entityArray[theEnemy->position.h][theEnemy->position.v] = nil;
  524.             entityArray[newh][newv] = theEnemy;
  525.             DrawTile(newh, newv);
  526.             DrawTile(theEnemy->position.h, theEnemy->position.v);
  527.             theEnemy->position.h = newh;
  528.             theEnemy->position.v = newv;
  529.         }
  530.         break;
  531.  
  532.          case wall:
  533. //        case enemy:
  534. //        case gold:
  535. //        case exitPos:
  536. //        case tempEnemy: 
  537.             ;                                            /*Don't move to any of these!*/
  538.     }; /*case*/
  539. } /*MoveEnemy*/
  540.  
  541.  
  542. /* Initialize - create window, load graphics */
  543.  
  544. static void InitDungeon()
  545. {
  546.     Rect windowRectangle;
  547.  
  548. /*Set up the window*/
  549.     SetRect(&windowRectangle, 50, 50, 50 + kArraySizeH * kTileSizeH, 50 + kArraySizeV * kTileSizeV + kScoreFieldHeight);
  550.     myWindow = NewCWindow(nil, &windowRectangle, "\pDungeon E", true, documentProc, (WindowPtr)-1L, false, 0);
  551.     SetPort(myWindow);
  552.  
  553.     qd.randSeed = TickCount ();    /*Seed the random number generator*/
  554.  
  555. /*Load all pictures*/
  556.     floorTile = GetPicture(128);            /*PICT resource #128.*/
  557.     playerTile = GetPicture(129);            /*PICT resource #129.*/
  558.     enemyTile = GetPicture(130);            /*PICT resource #130.*/
  559.     goldTile = GetPicture(131);                /*PICT resource #131.*/
  560.     wallTile = GetPicture(132);                /*PICT resource #132.*/
  561.     exitTile = GetPicture(133);                /*PICT resource #133.*/
  562. } /*InitDungeon*/
  563.  
  564.  
  565. /* Set up for a new game */
  566.  
  567. static void NewGame()
  568. {
  569. /* Fill in the tileArr array */
  570.         CreateLevel();
  571.  
  572. /* Start with a healthy player */
  573.         playerHitPoints = 5;
  574.         gGold = 0;
  575.         gExperience = 0;
  576. } /*NewGame*/
  577.  
  578.  
  579. /* ValidMove checks if a tile clickedTile is inside the array bounds *and* near the player */
  580.  
  581. static Boolean ValidMove(Point clickedTile)
  582. {
  583. /* Valid tile?*/
  584.     if ( clickedTile.h >= 0 )
  585.         if ( clickedTile.v >= 0 )
  586.             if ( clickedTile.h < kArraySizeH )
  587.                 if ( clickedTile.v < kArraySizeV )
  588. /* OK, we are inside the game area, clicking in some space! Is it next to the player?*/
  589.                     if ( clickedTile.h >= player->position.h - 1 )
  590.                         if ( clickedTile.h <= player->position.h + 1 )
  591.                             if ( clickedTile.v >= player->position.v - 1 )
  592.                                 if ( clickedTile.v <= player->position.v + 1 ) 
  593.                                     return true;
  594. /* If that wasn't the case, it wasn't vcalid; return false! */
  595.     return false;
  596. } /*ValidMove*/
  597.  
  598.  
  599. /* Try to move the player to the position where we clicked. */
  600.  
  601. static void MovePlayer(Point clickedTile)
  602. {
  603.     short h, v;
  604.     GameEntityPtr    theEntity;
  605.  
  606. /* Valid move?*/
  607.     if (ValidMove(clickedTile))
  608. /* Yes! What is there? */
  609.     {
  610.         if (entityArray[clickedTile.h][clickedTile.v] != nil)
  611.         {
  612.             switch (entityArray[clickedTile.h][clickedTile.v]->kind)
  613.             {
  614.                 case enemyEntity:
  615.                     if ( Rand(10) > 4 )
  616.                     { /*Hit! Play a "monster died" sound*/
  617.                         
  618.                         entityArray[clickedTile.h][clickedTile.v]->value--;
  619.                         if (entityArray[clickedTile.h][clickedTile.v]->value <= 0)
  620.                         {    /* Monster died! */
  621.                             if ( noErr != SndPlay(nil, (SndListHandle)GetNamedResource('snd ', "\pEnemy died"), false) )
  622.                                 ;
  623.                             /* Walk to the space where the monster was.*/
  624.                             /*tileArray[player->position.h][player->position.v] = empty;
  625.                             tileArray[clickedTile.h][clickedTile.v] = player;*/
  626.                             
  627.                             KillEntity(entityArray[clickedTile.h][clickedTile.v]);
  628.                             entityArray[player->position.h][player->position.v] = nil;
  629.                             entityArray[clickedTile.h][clickedTile.v] = player;
  630.         
  631.                             DrawTile(player->position.h, player->position.v);
  632.                             DrawTile(clickedTile.h, clickedTile.v);
  633.                             player->position = clickedTile;
  634.  
  635. /* Killed a monster! Get experience! Let's say one point for the troll! */
  636.                             AddScore(0, 1);
  637.                         }
  638.                         else    /* Monster still lives! */
  639.                         {
  640.                         }
  641.                     }
  642.                     else
  643.                     { /*Miss! Play the "miss" sound. */
  644.                     if ( noErr != SndPlay(nil, (SndListHandle)GetNamedResource('snd ', "\pEnemy miss"), false) )
  645.                         ;
  646.                     }
  647.                     break;
  648.                 case goldEntity:
  649. /* Gold - add score! */
  650.                     AddScore(entityArray[clickedTile.h][clickedTile.v]->value, 0);
  651.                     
  652.                     KillEntity(entityArray[clickedTile.h][clickedTile.v]);
  653.                     entityArray[player->position.h][player->position.v] = nil;
  654.                     entityArray[clickedTile.h][clickedTile.v] = player;
  655.     
  656.                     DrawTile(player->position.h, player->position.v);
  657.                     DrawTile(clickedTile.h, clickedTile.v);
  658.                     player->position = clickedTile;
  659.                     if ( noErr != SndPlay(nil, (SndListHandle)GetNamedResource('snd ', "\pMoney"), false) )
  660.                         ;
  661.                     break;
  662.             } /* case kind */
  663.         }
  664.         else
  665.             switch ( tileArray[clickedTile.h][clickedTile.v] )
  666.             {
  667.             case empty:
  668.                 entityArray[player->position.h][player->position.v] = nil;
  669.                 entityArray[clickedTile.h][clickedTile.v] = player;
  670.     
  671.                 DrawTile(player->position.h, player->position.v);
  672.                 DrawTile(clickedTile.h, clickedTile.v);
  673.                 player->position = clickedTile;
  674.                 break;
  675.             case wall: 
  676.                 SysBeep(1);
  677.                 break;
  678.             case exitPos: 
  679.                 entityArray[player->position.h][player->position.v] = nil;
  680.                 entityArray[clickedTile.h][clickedTile.v] = player;
  681.     
  682.                 DrawTile(player->position.h, player->position.v);
  683.                 DrawTile(clickedTile.h, clickedTile.v);
  684.                 player->position = clickedTile;
  685.                 if ( noErr != SndPlay(nil, (SndListHandle)GetNamedResource('snd ', "\pNext level"), false) )
  686.                     ;
  687.                 CreateLevel();    /* Don't quit - make a new level instead */
  688.                 break;
  689.             }; /*case*/
  690.     }; /*valid move*/
  691.     
  692.     ShowAroundPlayer();
  693.     
  694. /* Monsters are allowed to move now!*/
  695.  
  696.     theEntity = gEntityList;
  697.     while (theEntity != nil)
  698.     {
  699.         if (theEntity->kind == enemyEntity)
  700.             MoveEnemy(theEntity);
  701.         theEntity = theEntity->next;
  702.     }
  703.  
  704. } /*MovePlayer*/
  705.  
  706.  
  707. /* Handle mouse downs */
  708.  
  709. static void DoMouse(Point clickPoint, long mods)
  710. {
  711.     Point clickedTile;
  712.     short h, v;
  713.  
  714. /* If the hero is dead, we can't move! */
  715.     if ( playerHitPoints < 1 )
  716.     {
  717.         SysBeep(1);
  718.         return;
  719.     };
  720.  
  721. /*Convert clickPoint to local coordinates*/
  722.     GlobalToLocal(&clickPoint);
  723. /*Convert it to coordinates in the arrays.*/
  724.     clickedTile.h = clickPoint.h / kTileSizeH;
  725.     clickedTile.v = clickPoint.v / kTileSizeV;
  726.     MovePlayer(clickedTile);
  727. } /*DoMouse*/
  728.  
  729.  
  730. /* Empty stub for a background task. */
  731.  
  732. static void DoBackground()
  733. {
  734. } /*DoBackground*/
  735.  
  736.  
  737. /* Add two points - simplifies the keydown handler */
  738.  
  739. static Point AddPoints(Point p1,Point p2)
  740. {
  741.     Point dest;
  742.  
  743.     dest.h = p1.h + p2.h;
  744.     dest.v = p1.v + p2.v;
  745.     return dest;
  746. }; /*AddPoints*/
  747.  
  748.  
  749. /* Handle key downs */
  750.  
  751. static void DoKey(char theKey, long mods)
  752. {
  753. /* If the hero is dead, we can't move! */
  754.     if ( playerHitPoints < 1 )
  755.         {
  756.             SysBeep(1);
  757.             return;
  758.         };
  759.  
  760. /* For now, we use a hard-coded key mapping. */
  761.     switch ( theKey )
  762.         {
  763.         case 'd':
  764.         case '6': 
  765.             MovePlayer(AddPoints(player->position, directionTable[0]));break;
  766.         case 'e':
  767.         case '9': 
  768.             MovePlayer(AddPoints(player->position, directionTable[1]));break;
  769.         case 'w':
  770.         case '8': 
  771.             MovePlayer(AddPoints(player->position, directionTable[2]));break;
  772.         case 'q':
  773.         case '7': 
  774.             MovePlayer(AddPoints(player->position, directionTable[3]));break;
  775.         case 'a':
  776.         case '4': 
  777.             MovePlayer(AddPoints(player->position, directionTable[4]));break;
  778.         case 'z':
  779.         case '1': 
  780.             MovePlayer(AddPoints(player->position, directionTable[5]));break;
  781.         case 'x':
  782.         case '2': 
  783.             MovePlayer(AddPoints(player->position, directionTable[6]));break;
  784.         case 'c':
  785.         case '3': 
  786.             MovePlayer(AddPoints(player->position, directionTable[7]));break;
  787.         default:
  788.             SysBeep(1);
  789.     }; /*case*/
  790. }; /*DoKey*/
  791.  
  792.  
  793. /* Handle selections in the File menu */
  794.  
  795. static void DoFileMenu(short item)
  796. {
  797.     switch ( item )
  798.         {
  799.         case 1: 
  800.             NewGame();break;
  801.         case 3: 
  802.             gDone = true;break;
  803.     }; /*case*/
  804. }; /*DoFileMenu*/
  805.  
  806.  
  807. /* The "About" item was selected */
  808.  
  809. static void DoAbout()
  810. {
  811.     short ignore;
  812.  
  813.     ignore = Alert(128, nil);
  814. }; /*DoAbout*/
  815.  
  816.  
  817. /* Handle update events */
  818.  
  819. static void DoUpdate()
  820. {
  821.         short h, v;
  822.  
  823.         BeginUpdate(myWindow);
  824. /*Draw all tiles!*/
  825.         for ( h = 0 ; h < kArraySizeH ; h++)
  826.             for ( v = 0 ; v < kArraySizeV ; v++)
  827.                 DrawTile(h, v);
  828.         
  829.         DrawScore();
  830.         
  831.         EndUpdate(myWindow);
  832. }; /*DoUpdate*/
  833.  
  834.  
  835. /* Handle menu selections */
  836.  
  837. static void DoMenuSelection(long mSelect)
  838. {
  839.     short menuID;
  840.     short menuItem;
  841.     GrafPtr savePort;
  842.     Str255 name;
  843.     short ignore;
  844.  
  845.     menuID = HiWord(mSelect);
  846.     menuItem = LoWord(mSelect);
  847.  
  848.     switch ( menuID )
  849.         {
  850.         case appleID: 
  851.             if ( menuItem == 1 )
  852.                 DoAbout();
  853.             else
  854.                 {
  855.                     GetPort(&savePort);
  856.                     GetItem(GetMHandle(appleID), menuItem, name);
  857.                     ignore = OpenDeskAcc(name);
  858.                     SetPort(savePort);
  859.                 };
  860.             break;
  861.         case fileID: 
  862.             DoFileMenu(menuItem);break;
  863.         default:
  864.     ;}; /*case*/
  865.     HiliteMenu(0);
  866. }; /*DoMenuSelection*/
  867.  
  868.  
  869. /* Set up menus */
  870.  
  871. static void SetupMenus()
  872. {
  873.     MenuHandle appleMenu, fileMenu;
  874.     Str255 tempStr;
  875.  
  876.     tempStr[0]=1; tempStr[1] = (char)20;
  877.     appleMenu = NewMenu(appleID, tempStr); /*Apple menu symbol*/
  878.     InsertMenu(appleMenu, 0);
  879.     AppendMenu(appleMenu, "\pAbout Dungeon…;(-");
  880.     AppendResMenu(appleMenu, 'DRVR');
  881.  
  882.     fileMenu = GetMenu(128);
  883.     InsertMenu(fileMenu, 0);
  884.     DrawMenuBar ();
  885.  
  886. }; /*SetupMenus*/
  887.  
  888.  
  889. /* Main event loop */
  890.  
  891. static void MainLoop(void)
  892. {
  893. #define    kSleep 5 /*Real programs may modify the sleep time depending on whether or not they are in the front*/
  894.  
  895.     EventRecord    theEvent;
  896.     char    theKey;
  897.     long    whatSelection;
  898.     short    whichPart;
  899.     WindowPtr    whichWindow;
  900.     Rect    r;
  901.     Point    diskInitPt = {40,40};
  902.  
  903. /*Get the next event with WaitNextEvent.*/
  904.     if ( WaitNextEvent(everyEvent, &theEvent, kSleep, nil) )
  905.         switch ( theEvent.what )
  906.         {
  907.             case mouseDown: 
  908. /*We must find out what kind of mouse down this was.*/
  909.                     whichPart = FindWindow(theEvent.where, &whichWindow);
  910.                     switch ( whichPart )
  911.                     {
  912.                         case inMenuBar:
  913. /*Click in menu bar. Let the system call MenuSelect track it.*/
  914.                             whatSelection = MenuSelect(theEvent.where);
  915.                             DoMenuSelection(whatSelection);    /*Our own routine for handling menu selections*/
  916.                             break;
  917.                         case inSysWindow:
  918. /*Click in some window that isn't ours*/
  919.                             SystemClick(&theEvent, whichWindow);
  920.                             break;
  921.                         case inGoAway:
  922. /*Click in close box of window. For "desk accessory"-style games, that is a good quit signal*/
  923.                             if ( (TrackGoAway(whichWindow, theEvent.where)) )
  924.                                 gDone = true;
  925.                             break;
  926.                         case inDrag:
  927. /*Drag a window*/
  928.                             if ( (whichWindow != FrontWindow ()) && ((theEvent.modifiers, cmdKey) == 0) )
  929.                                 SelectWindow(whichWindow);
  930.                             DragWindow(whichWindow, theEvent.where, &qd.screenBits.bounds);
  931.                             break;
  932.                         case inGrow: 
  933.                             ;  /*Ignored - we don't resize*/
  934.                             break;
  935.                         case inContent:
  936. /*Click in the window.*/
  937.                             if ( (whichWindow != FrontWindow ()) )
  938.                                 SelectWindow(whichWindow);
  939.                             else
  940.                                 DoMouse(theEvent.where, theEvent.modifiers); /*Go to application-specific mouse down handling*/
  941.                     }; /*case whichPart*/
  942.                 break; /*mouseDown*/
  943.             case keyDown:
  944.             case autoKey:
  945. /*If the command key is pressed, it is a menu selection*/
  946.                     theKey = (char)(theEvent.message & charCodeMask);
  947.                     if ( ((theEvent.modifiers & cmdKey) != 0) )
  948.                         DoMenuSelection(MenuKey(theKey));    /*Our own routine for handling menu selections*/
  949.                     else
  950. /*Otherwise, it's a normal key down.*/
  951.                         DoKey(theKey, theEvent.modifiers);
  952.                     break;
  953.             case updateEvt:
  954. /*Find out what event the update event is for.*/
  955.                 if ( (WindowPtr)theEvent.message == myWindow )
  956.                     DoUpdate();
  957.                 break;
  958.             case diskEvt:
  959. /*Handle bad disk insertions*/
  960.                 if (HiWord(theEvent.message) != noErr)
  961.                 {
  962.                     DILoad();
  963.                     DIBadMount(diskInitPt, theEvent.message);
  964.                     DIUnload();
  965.                 }
  966.                 break;
  967.             default: /*Other events are ignored*/
  968.         ;}; /*case*/
  969.  
  970. /*For each turn to the event loop, we may to call some "background" process, e.g. animations.*/
  971.     DoBackground();
  972. } /*MainLoop*/
  973.  
  974.  
  975. /* Standard inits */
  976.  
  977. static void InitToolbox(void) {
  978.     InitGraf (&qd.thePort);
  979.     InitFonts ();
  980.     FlushEvents (everyEvent,0);
  981.     InitWindows ();
  982.     InitMenus ();
  983.     TEInit ();
  984.     InitDialogs (nil);
  985.     InitCursor ();
  986. }
  987.  
  988.  
  989. /* Main program */
  990.  
  991. void main(void)
  992. {
  993.     InitToolbox();
  994.     InitDungeon();
  995.     SetupMenus();
  996.     NewGame();
  997. /*Initializations done! Run the game loop until the game ends.*/
  998.     do
  999.         {
  1000.         MainLoop();
  1001.     } while (!  gDone);
  1002. } /*Dungeon*/
  1003.  
  1004.  
  1005. /*What's left for making a real game of it?*/
  1006. /*- Animations*/
  1007. /*- Several levels*/
  1008. /*- Faster drawing*/
  1009. /*- Asynch sound*/
  1010. /*- More objects, i.e. weapons, monsters, treasures…*/